#include "mvi.hpp"
#include <cstddef>
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <ncurses.h>
#include <ostream>
#include <stdexcept>
#include <string>
using std::cout;
using std::endl;

Mvi::Mvi(const std::string &file) {
  x = y = 0;
  mode = 'n';
  status = "NOR"; // stand for normal mode in vim
  section = {};

  if (file.empty()) {
    filename = file;
  } else {
    filename = file;
  }

  open();
  initscr();
  noecho();
  cbreak();
  keypad(stdscr, true);
  use_default_colors();
  start_color();
} // ctor end

Mvi::~Mvi() {
  refresh();
  endwin();
}

void Mvi::run() {
  while (mode != 'q') {
    update();
    statusline();
    print();
    int c = getch();
    input(c);
  }
}

void Mvi::update() {
  switch (mode) {
  case 'n':
    status = "NOR";
    break;
  case 'i':
    status = "INS";
    break;
  case 'q':
    break;
  }

  section = " COLS: " + std::to_string(x) + " | ROWS: " + std::to_string(y) +
            " | FILE: " + filename;
}

void Mvi::statusline() {
  if (mode == 'n') {
    init_pair(1, COLOR_MAGENTA, COLOR_WHITE);
  } else {
    init_pair(1, COLOR_GREEN, COLOR_BLACK);
  }

  attron(A_REVERSE);
  attron(A_BOLD);
  attron(COLOR_PAIR(1));

  for (int i{}; i < COLS; ++i) {
    mvprintw(LINES - 1, i, " ");
  }

  mvprintw(LINES - 1, 0, status.c_str());

  mvprintw(LINES - 1, COLS - section.length(), section.c_str());

  attroff(COLOR_PAIR(1));
  attroff(A_BOLD);
  attroff(A_REVERSE);
}

void Mvi::input(int c) {
  switch (c) {
  case KEY_UP:
    up();
    return;
  case KEY_LEFT:
    left();
    return;
  case KEY_RIGHT:
    right();
    return;
  case KEY_DOWN:
    down();
    return;
  }

  switch (mode) {
  case 27:
  case 'n':
    switch (c) {
    case 'q':
      mode = 'q';
      break;
    case 'i':
      mode = 'i';
      break;
    case 'w':
      mode = 'w';
      save();

      cout << endl;
      refresh();
      endwin();
      std::printf("Saved\n");
      exit(0);
      break;
    }
    break;
  case 'i':
    switch (c) {
    case 27: // 27 is ESC
      mode = 'n';
      break;
    case 127:
    case KEY_BACKSPACE:
      if (x == 0 && y > 0) {
        x = lines[y - 1].length();
        lines[y - 1] += lines[y];
        remove(y);
        up();
      } else if (x > 0) {
        lines[y].erase(--x, 1);
      }
      break;
    case KEY_DC:
      if (x == lines[y].length() && y != lines.size() - 1) {
        lines[y] += lines[y + 1];
      } else {
        lines[y].erase(x, 1);
      }
      break;
    case KEY_ENTER:
    case 10:
      if (x < lines[y].length()) {
        insert(lines[y].substr(x, lines[y].length() - x), y + 1);
        lines[y].erase(x, lines[y].length() - x);
      } else {
        insert("", y + 1);
      }
      x = 0;
      down();
      break;
    case KEY_BTAB:
    case KEY_CTAB:
    case KEY_STAB:
    case KEY_CATAB:
    case 9:
      lines[y].insert(x, 2, ' ');
      x += 2;
      break;
    default:
      lines[y].insert(x, 1, c);
      ++x;
    }
  }
}

void Mvi::print() {
  for (size_t i{}; i < (size_t)LINES - 1; ++i) {
    if (i >= lines.size()) {
      move(i, 0);
      clrtoeol();
    } else {
      mvprintw(i, 0, lines[i].c_str());
    }
    clrtoeol();
  }

  move(y, x);
}

void Mvi::remove(int number) { lines.erase(lines.begin() + number); }

std::string Mvi::tabs(std::string &line) {
  std::size_t tab = line.find('\t');
  return tab == line.npos ? line : tabs(line.replace(tab, 1, " "));
}

void Mvi::insert(std::string line, int number) {
  line = tabs(line);
  lines.insert(lines.cbegin() + number, line);
}

void Mvi::append(std::string &line) {
  line = tabs(line);
  lines.push_back(line);
}

void Mvi::up() {
  if (y > 0) {
    --y;
  }
  if (x >= lines[y].length()) {
    x = lines[y].length();
  }

  move(y, x);
}

void Mvi::left() {
  if (x > 0) {
    --x;
    move(y, x);
  }
}

void Mvi::right() {
  if ((int)x <= COLS && x <= lines[y].length() - 1) {
    ++x;
    move(y, x);
  }
}

void Mvi::down() {
  if ((int)y < LINES && y < lines.size() - 1) {
    ++y;
  }

  if (x >= lines[y].length()) {
    x = lines[y].length();
  }

  move(y, x);
}

void Mvi::open() {
  if (std::filesystem::exists(filename)) {
    std::ifstream ifile(filename);
    if (ifile.is_open()) {
      while (!ifile.eof()) {
        std::string buf;
        std::getline(ifile, buf);
        append(buf);
      }
    } else {
      throw std::runtime_error(
          "[ERR]: Can not open file, permission denied, try root.");
    }
  } else {
    std::string str{};
    append(str);
  }
}

void Mvi::save() {
  std::ofstream ofile(filename);
  if (ofile.is_open()) {
    for (size_t i{}; i < lines.size(); ++i) {
      ofile << lines[i] << '\n';
    }

    ofile.close();
  } else {
    refresh();
    endwin();

    throw std::runtime_error(
        "[ERR]: Can not save file, permission denied, try root.");
  }
}
